package ifm4j.core;

import ifm4j.collections.treeCollections.INonLeaf;
import ifm4j.collections.treeCollections.Tree;
import ifm4j.collections.treeCollections.TreeCollectionException;
import ifm4j.collections.treeCollections.TreeObject;
import ifm4j.collections.treeCollections.TreeRoot;
import ifm4j.collections.treeCollections.factories.TreeFactory;
import ifm4j.collections.treeCollections.factories.TreeFactoryException;
import ifm4j.collections.treeCollections.factories.TreeFactoryTable;
//import ifm4j.core.arguments.ConstraintCreatedArgs;
//import ifm4j.core.arguments.FeatureCreatedArgs;
//import ifm4j.core.arguments.RelationshipCreatedArgs;
import ifm4j.core.featureModelInteraface.IFeature;
import ifm4j.core.featureRelationTypes.FMCrossTreeConstraintType;
import ifm4j.core.featureRelationTypes.FMTreeGroupRelationshipType;
import ifm4j.utils.Identifier;
import ifm4j.utils.InvalidIdentifierException;

import java.util.Hashtable;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

public final class FeatureModel extends Tree{
	public final static TreeFactory TREE_FACTORY = new TreeFactory(FeatureModel.class.getName()) {

        @Override
        public Tree ConstructTree(Object[] args) {
            return new FeatureModel();
        }

        @Override
        public Tree ConstructTreeWithRoot(TreeRoot root, Object[] args) {
            return new FeatureModel((FeatureTreeRoot) root);
        }

        @Override
        public void xmlParser(Element elem, Tree tree) {
            if (!(tree instanceof FeatureModel)) {
                return;
            }

            FeatureModel featureModel = (FeatureModel) tree;
            NodeList nodeList = elem.getChildNodes();
            int len = nodeList.getLength();

            for (int i = 0; i != len; ++i) {
                Node node = nodeList.item(i);
                if (!(node instanceof Element)) {
                    continue;
                }
                Element xcons = (Element) node;
                if (!xcons.getNodeName().equalsIgnoreCase(
                        CrossTreeConstraint.NAME)) {
                    continue;
                }
                try {
                    CrossTreeConstraint con = CrossTreeConstraint
                            .xmlParser(xcons);
                    featureModel.crossTreeConstraints.put(con.getIdentifier(),
                            con);
                } catch (InvalidIdentifierException e) {
                    continue;
                }
            }
        }
    };

    static {
        TreeFactoryTable factoryTable = TreeFactoryTable.getFactoryTable();
        try {
            TREE_FACTORY.addTreeObjectFactory(FeatureTreeNode.FACTORY);
            TREE_FACTORY
                    .addTreeObjectFactory(FeatureTreeGroupRelationship.FACTORY);
            TREE_FACTORY.addTreeObjectFactory(FeatureTreeRoot.FACTORY);
        } catch (TreeFactoryException e) {

            e.printStackTrace();
        }
        factoryTable.addTreeFactory(TREE_FACTORY);
    }

    public static final String STRING_EMPTY = "";

    public static final int DEFAULT_FEATURE_WIDTH = 80;

    public static final int DEFAULT_FEATURE_HEIGHT = 60;

    private Hashtable<Identifier, CrossTreeConstraint> crossTreeConstraints;

    /**
     * Construct new feature model
     */
    public FeatureModel() {
        super();
        crossTreeConstraints = new Hashtable<Identifier, CrossTreeConstraint>();
    }

    /**
     * Construct new feature model with feature tree root
     * 
     * @param root
     */
    public FeatureModel(FeatureTreeRoot root) {
        super(root);
        crossTreeConstraints = new Hashtable<Identifier, CrossTreeConstraint>();
    }

    /**
     * get Feature Tree Node
     * 
     * @return
     */
    public IFeature getFeatureNode(Identifier id) {
        TreeObject obj = this.getTreeObject(id);
        if (!(obj instanceof IFeature)) {
            return null;
        }
        return (IFeature) obj;
    }

    /**
     * Get XML document.
     * 
     * @return
     */
    protected Document getXmlDocument() {
        return this.xmlDocument;
    }

    /**
     * Parse feature model to XML
     */
    @Override
    public Element toXElem() throws DOMException, TreeCollectionException {
        Element xelem = super.toXElem();
        for (CrossTreeConstraint cons : crossTreeConstraints.values()) {
            xelem.appendChild(cons.toXElem());
        }
        return xelem;
    }

    /**
     * Create feature node
     * 
     * @param parent
     * @param featureName
     * @return
     * @throws FeatureModelException
     */
    public FeatureTreeNode createFeatureNode(FeatureTreeGroupRelationship parent, String featureName) throws FeatureModelException {
//        FeatureCreatedArgs args = new FeatureCreatedArgs(this, parent, featureName);
        TreeObject obj = null;
        try {
//            this.beforeProcess(this, args);
            obj = this.createNewInstance(FeatureTreeNode.class.getName(),
                    parent, featureName);
            if (!(obj instanceof FeatureTreeNode)) {
//                this.afterProcess(this, args);
                return null;
            }

        } catch (Throwable e) {
            throw new FeatureModelException(e.getMessage());
        } finally {
//            this.afterProcess(this, args);
        }
        return (FeatureTreeNode) obj;
    }

    /**
     * Create group relationship
     * 
     * @param parent
     * @param gType
     * @return
     * @throws FeatureModelException
     * @throws TreeCollectionException
     */
    public FeatureTreeGroupRelationship createGroupRelationship(IFeature parent, FMTreeGroupRelationshipType gType) throws FeatureModelException {
//        RelationshipCreatedArgs args = new RelationshipCreatedArgs(this, gType);
        TreeObject obj = null;
        try {
//            this.beforeProcess(this, args);
            obj = this.createNewInstance(
                    FeatureTreeGroupRelationship.class.getName(),
                    (INonLeaf) parent, gType);
            if (!(obj instanceof FeatureTreeGroupRelationship)) {
                return null;
            }
        } catch (Throwable e) {
            throw new FeatureModelException(e.getMessage());
        } finally {
//            this.afterProcess(this, args);
        }
        return (FeatureTreeGroupRelationship) obj;
    }

    /**
     * Create constraint on Node A and Node B
     * 
     * @param idA
     * @param idB
     * @param consType
     * @return
     */
    public CrossTreeConstraint createConstraint(Identifier idA, Identifier idB, FMCrossTreeConstraintType consType) {
//        ConstraintCreatedArgs args = new ConstraintCreatedArgs(this, idA, idB, consType);
        CrossTreeConstraint cons = null;
        try {
//            this.beforeProcess(this, args);

            Identifier id = this.generateId();
            cons = new CrossTreeConstraint(id, consType, idA, idB);
            this.crossTreeConstraints.put(id, cons);
            cons.setFeatureModelOwner(this);
        } finally {
//            this.afterProcess(this, args);
        }
        return cons;
    }
}